package entity

import (
	"errors"
	"sync"
	"sync/atomic"

	"gitee.com/night-tc/gobige/common"
	"gitee.com/night-tc/gobige/global"
	"gitee.com/night-tc/gobige/logger"
	"gitee.com/night-tc/gobige/msgdef"
	"gitee.com/night-tc/gobige/msgdef/protomsg"
	"gitee.com/night-tc/gobige/msghandler"
	"gitee.com/night-tc/gobige/sess"
)

/*
带链接的Entity的组件
*/
type IClientSess interface {
	IBase
	msghandler.IMsgHandler
	//获取连接
	GetClientSess() sess.INetSess
	// 设置链接
	SetClientSess(v sess.INetSess)
	// 关闭连接
	CloseClientSess()
	// 发消息给客户端的二进制消息
	PostBuf(data []byte) error
	//发消息出去
	Post(stye global.ServerTypeEnum, msg msgdef.IMsg) error
	// DelayPost 延迟发送消息, 在帧尾发送
	DelayPost(stye global.ServerTypeEnum, msg msgdef.IMsg)

	//获取服务器信息
	GetSrvByStype(styp uint32) (*EntitySrvInfo, error)
	// 刷新对象路由信息
	RefreshSrvIDS()
	// 本对象有没有连接指点的服务器
	IsExistSID(srvid uint64) bool

	// 删除目标服务器上的本对象副本
	DestroyEntityByStype(stye global.ServerTypeEnum, callbackf global.RouteCallBackF) error
}

// 带链接的Entity的组件
type EntitySess struct {
	EntityBase
	*msghandler.MsgHandler

	//后代对象 组件内使用
	realPtr IEntity

	//是否为自由对象，不是自由对象时，需要redis里注册路由
	flyEntity bool
	//对象身上所有需要垮协程的数量读写的时候，用到的锁
	Mux sync.RWMutex
	//对象Redis操作器
	utilRedis *RedisUtilEntity
	//对象在各个服务器的地图信息
	srvIDS []*EntitySrvInfo

	// 直连模式, ServerTypeClient类型不经过转发
	IsDirectMode atomic.Bool
	//需要一个连接器
	clientSess sess.INetSess
}

// OnCreated 实体创建时调用
func (this *EntitySess) OnCreated(fly bool, real IEntity,
	etype string, eid, dbid uint64, spacePtr IEntities, param ...interface{}) {
	this.EntityBase.OnCreated(etype, eid, dbid, spacePtr, param...)
	this.MsgHandler = msghandler.NewMsgHandler()
	this.flyEntity = fly
	this.realPtr = real
	if !this.flyEntity {
		//需要注册到Redis中
		this.utilRedis = NewRedisUtilEntity(this.entityID)
		this.srvIDS = make([]*EntitySrvInfo, 0, 8)
	}
}

//------------------------------------------------------------------------------------

// 获取连接
func (this *EntitySess) GetClientSess() sess.INetSess {
	this.Mux.RLock()
	defer this.Mux.RUnlock()
	return this.clientSess
}

// 设置链接
func (this *EntitySess) SetClientSess(v sess.INetSess) {
	this.Mux.Lock()
	defer this.Mux.Unlock()
	this.IsDirectMode.Store(true)
	this.clientSess = v
	if v == nil {
		return
	}
	this.clientSess.SetMsgHandler(this.MsgHandler)
}

// 关闭连接
func (this *EntitySess) CloseClientSess() {
	this.Mux.Lock()
	defer this.Mux.Unlock()
	if this.clientSess != nil {
		//不能直接关，因为什么死锁
		this.clientSess.DelayClose()
		this.clientSess = nil
	}
}

// 发消息给客户端的二进制消息
func (this *EntitySess) PostBuf(data []byte) error {
	if cs := this.GetClientSess(); cs != nil {
		logger.Debugf(logger.LogKey_EntitySess, "PostBuf Send to client sess %d,dbid:%d, data len %d", this.entityID, this.dbid, len(data))
		cs.SendBuf(data)
		return nil
	} else if this.IsDirectMode.Load() {
		return common.NewError(-1, "No sess.")
	} else {
		//表示自己没有链接需要发给getway服务器
		smd, err := this.GetSrvByStype(global.ServerTypeGateway)
		if err != nil {
			return err
		}
		return global.GetSrvInst().PostMsgToClientBuf(smd.SrvID, smd.SpaceID, this.entityID, data)
	}
}

func (this *EntitySess) Post(stye global.ServerTypeEnum, msg msgdef.IMsg) error {
	if msg == nil {
		return nil
	}
	if stye == global.ServerTypeClient {
		if cs := this.GetClientSess(); cs != nil {
			logger.Debugf(logger.LogKey_EntitySess, "Post Send to client sess %d,dbid:%d, msg %s", this.entityID, this.dbid, msg.Name())
			cs.Send(msg)
			return nil
		} else if this.IsDirectMode.Load() {
			return nil
		} else {
			//表示自己没有链接需要发给getway服务器
			smd, err := this.GetSrvByStype(global.ServerTypeGateway)
			if err != nil {
				return err
			}
			return global.GetSrvInst().PostMsgToClient(smd.SrvID, smd.SpaceID, this.entityID, msg)
		}

	}
	//找到目标服务器发给目标服务器
	smd, err := this.GetSrvByStype(stye)
	if err != nil {
		return err
	}
	return global.GetSrvInst().PostMsgToEntity(smd.SrvID, smd.SpaceID, this.entityID, msg)
}

// DelayPost 延迟发送消息, 在帧尾发送
func (this *EntitySess) DelayPost(stye global.ServerTypeEnum, msg msgdef.IMsg) {
	if ed, ok := this.realPtr.(IDelayCall); ok {
		ed.CallDelay(func() {
			if err := this.Post(stye, msg); err != nil {
				logger.Warn("Entity.DelayPost in Entity ", err)
			}
		})
	} else {
		this.GetEntities().CallDelay(func() {
			if err := this.Post(stye, msg); err != nil {
				logger.Warn("Entity.DelayPost in Entities ", err)
			}
		})
	}
}

//-----------------------------------------------------------------------------------

// 获取服务器信息
func (this *EntitySess) GetSrvByStype(styp uint32) (*EntitySrvInfo, error) {
	if this.flyEntity {
		return nil, ErrNtt_Fly.NewErr(nil, this.dbid, styp)
	}
	if sinfo := this.getSrvByStype(styp); sinfo != nil {
		return sinfo, nil
	}
	this.RefreshSrvIDS()
	if sinfo := this.getSrvByStype(styp); sinfo != nil {
		return sinfo, nil
	}
	return nil, ErrNtt_Stype_Not_Exist.NewErr(nil, this.dbid, styp)
}

// 刷新对象路由信息
func (this *EntitySess) RefreshSrvIDS() {
	if this.flyEntity {
		return
	}
	this.Mux.Lock()
	defer this.Mux.Unlock()
	li, err := this.utilRedis.GetSrvInfos()
	if err == nil {
		this.srvIDS = li
	}
}

// 本对象有没有连接指点的服务器
func (this *EntitySess) IsExistSID(srvid uint64) bool {
	if this.flyEntity {
		return false
	}
	this.Mux.RLock()
	defer this.Mux.RUnlock()
	for _, v := range this.srvIDS {
		if srvid == v.SrvID {
			return true
		}
	}
	return false
}

// 去指定服务器上创建副本
func (this *EntitySess) CreateEntityByStype(stye global.ServerTypeEnum, spid, sid uint64, callbackf global.RouteCallBackF) error {
	_, err := this.GetSrvByStype(stye)
	if err == nil {
		return ErrNtt_Stype_Exist.NewErr(nil, this.dbid, stye)
	}
	if !errors.Is(err, ErrNtt_Stype_Not_Exist) {
		return err
	}
	msg := new(protomsg.CreateEntityReqSrv)
	msg.DBID = this.GetDBID()
	msg.EntityID = this.GetEntityID()
	msg.EntityType = this.GetType()
	global.PostSrvAsync(sid, msg, callbackf,
		global.SrvSetEID(spid, this.entityID))
	logger.Debugf(logger.LogKey_Entity, "CreateEntityByStype pid:%d,nttid:%d,srv:%d,space:%d",
		this.GetDBID(), this.GetEntityID(), sid, spid)

	return nil
}

// 删除目标服务器上的本对象副本
func (this *EntitySess) DestroyEntityByStype(stye global.ServerTypeEnum, callbackf global.RouteCallBackF) error {
	sinfo, err := this.GetSrvByStype(stye)
	if err != nil {
		return err
	}
	msg := new(protomsg.DestroyEntityReqSrv)
	msg.EntityID = this.GetEntityID()
	// msg.SrcEntityID = this.GetEntityID()
	global.PostSrvAsync(sinfo.SrvID, msg, callbackf,
		global.SrvSetEID(sinfo.SpaceID, this.entityID))
	logger.Debugf(logger.LogKey_Entity, "DestroyEntityByStype pid:%d,nttid:%d,srv:%d,space:%d",
		this.GetDBID(), this.GetEntityID(), sinfo.SrvID, sinfo.SpaceID)

	return nil
}

// 获取服务器路由信息
func (e *EntitySess) GetSrvInfoAll() []*EntitySrvInfo {
	e.Mux.RLock()
	defer e.Mux.RUnlock()
	return e.srvIDS
}

// 设置服务器路由信息
func (e *EntitySess) SetSrvInfo(v []*EntitySrvInfo) {
	e.Mux.Lock()
	defer e.Mux.Unlock()
	e.srvIDS = v
}

/////////////////////////////////////////////////////////////////
/* 私有函数 */

/*
getSrvByStype 根据服务器类型查找对应的服务信息
@param styp uint32 服务器类型标识(使用global.ServerTypeSpace等常量)
@return *EntitySrvInfo 符合条件的服务信息实例指针，未找到返回nil
*/
func (this *EntitySess) getSrvByStype(styp uint32) *EntitySrvInfo {
	// 并发控制：加读锁并在函数返回时自动释放
	this.Mux.RLock()
	defer this.Mux.RUnlock()

	// 遍历所有服务实例进行匹配
	for _, v := range this.srvIDS {
		// 处理场景服务器特殊逻辑：需检查SpaceID字段
		if styp == global.ServerTypeSpace {
			// 如果是要去场景服务器，就判断哪个服务器有SpaceID
			if v.SpaceID != 0 {
				return v
			}
		} else if v.Stype == styp {
			return v
		}
	}
	return nil
}

// boardCastSrvInfo 向其他服务广播本服务的注册状态信息
// 参数说明:
//   - isreg: 注册标识，true表示服务注册，false表示服务注销
//
// 功能说明:
//   - 创建服务注册通知消息体，包含当前服务类型和注册状态
//   - 获取所有服务实例信息，过滤掉客户端服务和本类型服务
//   - 向符合条件的服务实例发送注册状态通知
func (this *EntitySess) boardCastSrvInfo(isreg bool) {
	// 构造注册通知消息，包含当前服务类型和注册状态
	msg := &protomsg.EntityRegNtf{
		SrvType: global.GetCfgInst().GetSrvType(),
		IsReg:   isreg,
	}

	// 获取所有服务实例信息
	srvli := this.GetSrvInfoAll()

	// 遍历服务列表进行过滤和消息发送
	for _, srvmd := range srvli {
		// 排除客户端服务和同类型服务，避免自循环
		if srvmd.Stype != global.ServerTypeClient && srvmd.Stype != global.GetCfgInst().GetSrvType() {
			// 异步发送注册状态通知到目标服务
			_ = this.Post(srvmd.Stype, msg)
		}
	}
}

// -------------------------------------------------------------
/* 实体创建、销毁时调用 */

// 注册本实体到redis中
func (this *EntitySess) RegEntity() (err error) {
	if !this.flyEntity {
		if this.IsDirectMode.Load() {
			if err = this.utilRedis.RegEntity(this.entityType, this.dbid); err != nil {
				return err
			}
			this.utilRedis.Expire()
		}

		defer this.boardCastSrvInfo(true)
		return this.utilRedis.RegSrvID(
			global.GetCfgInst().GetDefType(),
			global.GetCfgInst().GetServerID(),
			this.spaceID)
	}
	return nil
}

// 注销本实体
func (this *EntitySess) UnRegEntity() {
	if !this.flyEntity {
		if this.IsDirectMode.Load() &&
			global.GetCfgInst().GetSrvType() == global.ServerTypeGateway {
			//只有gateway销毁对像的时候，才可以注销
			/*
			   这里有一个问题，如果是用户被T的时候，
			   是不是应该把会有新的用户数据写入，
			   然后这边又在删除？
			   正常应该同一个对象应该链接在同一个gateway上面
			*/

			_ = this.utilRedis.UnregEntity()
		} else {
			_ = this.utilRedis.UnRegSrvID(
				global.GetCfgInst().GetSrvType(),
				global.GetCfgInst().GetServerID(),
				this.spaceID)
		}
		this.boardCastSrvInfo(false)
	}
	this.realPtr.(IClientSess).CloseClientSess()
}
